package no.nordicsemi.puckcentral.bluetooth.gatt;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.os.AsyncTask;
import org.droidparts.Injector;
import org.droidparts.bus.EventBus;
import org.droidparts.util.L;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import no.nordicsemi.puckcentral.bluetooth.gatt.operations.GattCharacteristicReadOperation;
import no.nordicsemi.puckcentral.bluetooth.gatt.operations.GattDescriptorReadOperation;
import no.nordicsemi.puckcentral.bluetooth.gatt.operations.GattOperation;
import no.nordicsemi.puckcentral.triggers.Trigger;
public class GattManager {
private ConcurrentLinkedQueue<GattOperation> mQueue;
private ConcurrentHashMap<String, BluetoothGatt> mGatts;
private GattOperation mCurrentOperation;
private HashMap<UUID, ArrayList<CharacteristicChangeListener>> mCharacteristicChangeListeners;
private AsyncTask<Void, Void, Void> mCurrentOperationTimeout;
public GattManager() {
mQueue = new ConcurrentLinkedQueue<>();
mGatts = new ConcurrentHashMap<>();
mCurrentOperation = null;
mCharacteristicChangeListeners = new HashMap<>();
}
public synchronized void cancelCurrentOperationBundle() {
L.v("Cancelling current operation. Queue size before: " + mQueue.size());
if(mCurrentOperation != null && mCurrentOperation.getBundle() != null) {
for(GattOperation op : mCurrentOperation.getBundle().getOperations()) {
mQueue.remove(op);
}
}
L.v("Queue size after: " + mQueue.size());
mCurrentOperation = null;
drive();
}
public synchronized void queue(GattOperation gattOperation) {
mQueue.add(gattOperation);
L.v("Queueing Gatt operation, size will now become: " + mQueue.size());
drive();
}
private synchronized void drive() {
if(mCurrentOperation != null) {
L.e("tried to drive, but currentOperation was not null, " + mCurrentOperation);
return;
}
if( mQueue.size() == 0) {
L.v("Queue empty, drive loop stopped.");
mCurrentOperation = null;
return;
}
final GattOperation operation = mQueue.poll();
L.v("Driving Gatt queue, size will now become: " + mQueue.size());
setCurrentOperation(operation);
if(mCurrentOperationTimeout != null) {
mCurrentOperationTimeout.cancel(true);
}
mCurrentOperationTimeout = new AsyncTask<Void, Void, Void>() {
@Override
protected synchronized Void doInBackground(Void... voids) {
try {
L.v("Starting to do a background timeout");
wait(operation.getTimoutInMillis());
} catch (InterruptedException e) {
L.v("was interrupted out of the timeout");
}
if(isCancelled()) {
L.v("The timeout was cancelled, so we do nothing.");
return null;
}
L.v("Timeout ran to completion, time to cancel the entire operation bundle. Abort, abort!");
cancelCurrentOperationBundle();
return null;
}
@Override
protected synchronized void onCancelled() {
super.onCancelled();
notify();
}
}.execute();
final BluetoothDevice device = operation.getDevice();
if(mGatts.containsKey(device.getAddress())) {
execute(mGatts.get(device.getAddress()), operation);
} else {
device.connectGatt(Injector.getApplicationContext(), true, new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
EventBus.postEvent(Trigger.TRIGGER_CONNECTION_STATE_CHANGED,
new ConnectionStateChangedBundle(
device.getAddress(),
newState));
if (status == 133) {
L.e("Got the status 133 bug, closing gatt");
gatt.close();
mGatts.remove(device.getAddress());
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
L.i("Gatt connected to device " + device.getAddress());
mGatts.put(device.getAddress(), gatt);
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
L.i("Disconnected from gatt server " + device.getAddress() + ", newState: " + newState);
mGatts.remove(device.getAddress());
setCurrentOperation(null);
gatt.close();
drive();
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
((GattDescriptorReadOperation) mCurrentOperation).onRead(descriptor);
setCurrentOperation(null);
drive();
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
setCurrentOperation(null);
drive();
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
((GattCharacteristicReadOperation) mCurrentOperation).onRead(characteristic);
setCurrentOperation(null);
drive();
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
L.d("services discovered, status: " + status);
execute(gatt, operation);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
L.d("Characteristic " + characteristic.getUuid() + "written to on device " + device.getAddress());
setCurrentOperation(null);
drive();
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
L.e("Characteristic " + characteristic.getUuid() + "was changed, device: " + device.getAddress());
if (mCharacteristicChangeListeners.containsKey(characteristic.getUuid())) {
for (CharacteristicChangeListener listener : mCharacteristicChangeListeners.get(characteristic.getUuid())) {
listener.onCharacteristicChanged(device.getAddress(), characteristic);
}
}
}
});
}
}
private void execute(BluetoothGatt gatt, GattOperation operation) {
if(operation != mCurrentOperation) {
return;
}
operation.execute(gatt);
if(!operation.hasAvailableCompletionCallback()) {
setCurrentOperation(null);
drive();
}
}
public synchronized void setCurrentOperation(GattOperation currentOperation) {
mCurrentOperation = currentOperation;
}
public BluetoothGatt getGatt(BluetoothDevice device) {
return mGatts.get(device);
}
public void addCharacteristicChangeListener(UUID characteristicUuid, CharacteristicChangeListener characteristicChangeListener) {
if(!mCharacteristicChangeListeners.containsKey(characteristicUuid)) {
mCharacteristicChangeListeners.put(characteristicUuid, new ArrayList<CharacteristicChangeListener>());
}
mCharacteristicChangeListeners.get(characteristicUuid).add(characteristicChangeListener);
}
public void queue(GattOperationBundle bundle) {
for(GattOperation operation : bundle.getOperations()) {
queue(operation);
}
}
public class ConnectionStateChangedBundle {
public final int mNewState;
public final String mAddress;
public ConnectionStateChangedBundle(String address, int newState) {
mAddress = address;
mNewState = newState;
}
}
}